import java.io.*;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Iterator;
import java.util.Scanner;

public class NIOFileCopyAndEcho {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入源文件路径: ");
        String sourcePath = scanner.nextLine();
        System.out.print("请输入目标文件路径: ");
        String destinationPath = scanner.nextLine();

        File sourceFile = new File(sourcePath);
        if (!sourceFile.exists()) {
            System.out.println("源文件不存在！");
            scanner.close();
            return;
        }

        // Test different buffer sizes
        int[] bufferSizes = {1024, 4096, 16384, 65536}; // 1KB, 4KB, 16KB, 64KB
        System.out.println("\n测试不同缓冲区大小的NIO复制性能：");
        for (int bufferSize : bufferSizes) {
            long timeTaken = copyFileNIO(sourcePath, destinationPath + "_nio_" + bufferSize + ".bin", bufferSize);
            System.out.println("缓冲区大小 " + bufferSize + " 字节，耗时: " + timeTaken + " 毫秒");
        }

        // Test memory-mapped file
        System.out.println("\n测试内存映射文件复制：");
        long timeTakenMapped = copyFileMemoryMapped(sourcePath, destinationPath + "_mapped.bin");
        System.out.println("内存映射复制耗时: " + timeTakenMapped + " 毫秒");

        // Test traditional IO
        System.out.println("\n测试传统IO复制：");
        long timeTakenIO = copyFileTraditionalIO(sourcePath, destinationPath + "_io.bin");
        System.out.println("传统IO复制耗时: " + timeTakenIO + " 毫秒");

        scanner.close();

        // Start echo server in a separate thread
        new Thread(() -> startEchoServer(8080)).start();

        // Give server time to start
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            System.err.println("线程中断: " + e.getMessage());
        }

        // Start echo client
        startEchoClient("localhost", 8080, "测试回显消息");

        // Discussion: NIO vs Traditional IO Performance
        // NIO在大文件处理中的优势：
        // 1. 非阻塞IO：NIO支持非阻塞操作，适合高并发场景
        // 2. 缓冲区管理：直接使用ByteBuffer，减少数据拷贝
        // 3. 内存映射：MappedByteBuffer直接映射文件到内存，适合大文件
        // 4. 通道机制：FileChannel提供高效的数据传输
        // 传统IO的劣势：流式操作涉及多次用户-内核空间拷贝，效率较低

        // 实际应用中平衡性能和资源：
        // 1. 缓冲区大小：选择合适的缓冲区（如4KB-64KB），避免过大占用内存
        // 2. 内存映射限制：大文件映射可能耗尽虚拟内存，需分块处理
        // 3. 资源管理：及时关闭通道和选择器，避免资源泄漏
        // 4. 并发控制：使用Selector管理多通道，优化冶
        // 5. 错误处理：确保异常不会中断关键操作
    }

    private static long copyFileNIO(String sourcePath, String destinationPath, int bufferSize) {
        long startTime = System.currentTimeMillis();
        long totalSize = new File(sourcePath).length();
        long copiedSize = 0;

        try (FileChannel sourceChannel = FileChannel.open(Paths.get(sourcePath), StandardOpenOption.READ);
             FileChannel destChannel = FileChannel.open(Paths.get(destinationPath), StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
            ByteBuffer buffer = ByteBuffer.allocate(bufferSize);

            while (sourceChannel.read(buffer) != -1) {
                buffer.flip();
                destChannel.write(buffer);
                copiedSize += buffer.limit();
                buffer.clear();
                // Display progress
                int progress = (int) ((copiedSize * 100.0) / totalSize);
                System.out.print("\rNIO复制进度 (缓冲区 " + bufferSize + "): " + progress + "%");
            }
        } catch (IOException e) {
            System.err.println("NIO复制错误: " + e.getMessage());
        }

        return System.currentTimeMillis() - startTime;
    }

    private static long copyFileMemoryMapped(String sourcePath, String destinationPath) {
        long startTime = System.currentTimeMillis();
        long totalSize = new File(sourcePath).length();
        long copiedSize = 0;

        try (FileChannel sourceChannel = FileChannel.open(Paths.get(sourcePath), StandardOpenOption.READ);
             FileChannel destChannel = FileChannel.open(Paths.get(destinationPath), StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
            long position = 0;
            long chunkSize = 1024 * 1024 * 128; // 128MB chunks to avoid memory issues

            while (position < totalSize) {
                long remaining = totalSize - position;
                long sizeToMap = Math.min(chunkSize, remaining);
                MappedByteBuffer buffer = sourceChannel.map(FileChannel.MapMode.READ_ONLY, position, sizeToMap);
                destChannel.write(buffer, position);
                position += sizeToMap;
                copiedSize += sizeToMap;
                // Display progress
                int progress = (int) ((copiedSize * 100.0) / totalSize);
                System.out.print("\r内存映射复制进度: " + progress + "%");
            }
        } catch (IOException e) {
            System.err.println("内存映射复制错误: " + e.getMessage());
        }

        return System.currentTimeMillis() - startTime;
    }

    private static long copyFileTraditionalIO(String sourcePath, String destinationPath) {
        long startTime = System.currentTimeMillis();
        long totalSize = new File(sourcePath).length();
        long copiedSize = 0;

        try (FileInputStream fis = new FileInputStream(sourcePath);
             FileOutputStream fos = new FileOutputStream(destinationPath)) {
            byte[] buffer = new byte[4096];
            int bytesRead;

            while ((bytesRead = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, bytesRead);
                copiedSize += bytesRead;
                // Display progress
                int progress = (int) ((copiedSize * 100.0) / totalSize);
                System.out.print("\r传统IO复制进度: " + progress + "%");
            }
        } catch (IOException e) {
            System.err.println("传统IO复制错误: " + e.getMessage());
        }

        return System.currentTimeMillis() - startTime;
    }

    private static void startEchoServer(int port) {
        ServerSocketChannel serverChannel = null;
        Selector selector = null;
        try {
            serverChannel = ServerSocketChannel.open();
            selector = Selector.open();
            serverChannel.bind(new java.net.InetSocketAddress(port));
            serverChannel.configureBlocking(false);
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("\n回显服务器启动，监听端口: " + port);

            while (true) {
                try {
                    selector.select(1000); // Add 1-second timeout to allow graceful shutdown
                    Iterator<SelectionKey> keys = selector.selectedKeys().iterator();

                    while (keys.hasNext()) {
                        SelectionKey key = keys.next();
                        keys.remove();

                        try {
                            if (key.isAcceptable()) {
                                SocketChannel client = serverChannel.accept();
                                if (client != null) {
                                    client.configureBlocking(false);
                                    client.register(selector, SelectionKey.OP_READ);
                                    System.out.println("客户端已连接: " + client.getRemoteAddress());
                                }
                            } else if (key.isReadable()) {
                                SocketChannel client = (SocketChannel) key.channel();
                                ByteBuffer buffer = ByteBuffer.allocate(1024);
                                int bytesRead;
                                try {
                                    bytesRead = client.read(buffer);
                                } catch (IOException e) {
                                    System.err.println("读取客户端数据失败: " + e.toString());
                                    closeClient(client, key);
                                    continue;
                                }
                                if (bytesRead == -1) {
                                    closeClient(client, key);
                                } else if (bytesRead > 0) {
                                    buffer.flip();
                                    try {
                                        client.write(buffer);
                                    } catch (IOException e) {
                                        System.err.println("向客户端写入数据失败: " + e.toString());
                                        closeClient(client, key);
                                        continue;
                                    }
                                    buffer.clear();
                                }
                            }
                        } catch (Exception e) {
                            System.err.println("处理SelectionKey时发生错误: " + e.toString());
                            if (key.channel() instanceof SocketChannel) {
                                closeClient((SocketChannel) key.channel(), key);
                            }
                        }
                    }
                } catch (IOException e) {
                    System.err.println("Selector选择失败: " + e.toString());
                }
            }
        } catch (IOException e) {
            System.err.println("服务器启动或运行失败: " + e.toString());
        } finally {
            try {
                if (selector != null && selector.isOpen()) selector.close();
                if (serverChannel != null && serverChannel.isOpen()) serverChannel.close();
            } catch (IOException e) {
                System.err.println("关闭服务器资源失败: " + e.toString());
            }
        }
    }

    private static void closeClient(SocketChannel client, SelectionKey key) {
        try {
            if (client != null && client.isOpen()) {
                System.out.println("关闭客户端连接: " + client.getRemoteAddress());
                client.close();
            }
            key.cancel();
        } catch (IOException e) {
            System.err.println("关闭客户端连接失败: " + e.toString());
        }
    }

    private static void startEchoClient(String host, int port, String message) {
        SocketChannel client = null;
        try {
            client = SocketChannel.open(new java.net.InetSocketAddress(host, port));
            client.configureBlocking(true); // Ensure blocking mode for simplicity
            ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
            client.write(buffer);
            buffer.clear();

            int bytesRead = client.read(buffer);
            if (bytesRead > 0) {
                buffer.flip();
                byte[] bytes = new byte[buffer.remaining()];
                buffer.get(bytes);
                System.out.println("客户端收到回显: " + new String(bytes));
            } else {
                System.out.println("未收到回显数据");
            }
        } catch (IOException e) {
            System.err.println("客户端错误: " + e.toString());
        } finally {
            try {
                if (client != null && client.isOpen()) client.close();
            } catch (IOException e) {
                System.err.println("关闭客户端失败: " + e.toString());
            }
        }
    }
}